import * as vscode from 'vscode';
import simpleGit, { SimpleGit } from 'simple-git';

export class GitService {
    private git: SimpleGit | null = null;

    constructor() {
        // No longer call initialize here
    }

    public async initialize(): Promise<boolean> {
        const workspaceFolders = vscode.workspace.workspaceFolders;
        if (workspaceFolders && workspaceFolders.length > 0) {
            const workspaceRoot = workspaceFolders[0].uri.fsPath;
            
            console.log('GitService is initializing at:', workspaceRoot);
            this.git = simpleGit(workspaceRoot);
            // Verify it's a git repo
            const isRepo = await this.git.checkIsRepo();
            if (!isRepo) {
                const errorMsg = 'The current workspace is not a Git repository.';
                console.error(errorMsg);
                vscode.window.showErrorMessage(errorMsg);
                this.git = null;
                return false;
            } else {
                 console.log('GitService successfully initialized.');
                 return true;
            }
        } else {
            const errorMsg = 'No workspace folder found. Git Commander Pro requires an open folder.';
            console.error(errorMsg);
            vscode.window.showErrorMessage(errorMsg);
            return false;
        }
    }

    public getGit(): SimpleGit {
        if (!this.git) {
            // This should not happen if initialize() is called and awaited properly.
            throw new Error("Git service has not been initialized. Please call initialize() first.");
        }
        return this.git;
    }

    public async getStatus(): Promise<import('simple-git').StatusResult> {
        const git = this.getGit();
        try {
            const status = await git.status();
            return status;
        } catch (error) {
            console.error('Failed to get git status from GitService:', error);
            vscode.window.showErrorMessage('Failed to execute git status.');
            // Re-throw or return a custom error object if you want to handle it further up the call stack
            throw error;
        }
    }

    public async getRepoRoot(): Promise<string> {
        return this.getGit().revparse(['--show-toplevel']);
    }

    public async commit(message: string): Promise<import('simple-git').CommitResult> {
        const git = this.getGit();
        try {
            await git.add('./*');
            const commitResult = await git.commit(message);
            vscode.window.showInformationMessage(`Committed successfully: ${commitResult.commit}`);
            return commitResult;
        } catch (error) {
            console.error('Failed to commit:', error);
            vscode.window.showErrorMessage('Failed to commit changes.');
            throw error;
        }
    }

    public async commitAndPush(message: string): Promise<void> {
        await this.commit(message);
        await this.push();
    }

    public async getHistory(maxCount = 50): Promise<import('simple-git').LogResult> {
        const git = this.getGit();
        try {
            const history = await git.log({ maxCount });
            return history;
        } catch (error) {
            console.error('Failed to get git history:', error);
            vscode.window.showErrorMessage('Failed to get git history.');
            throw error;
        }
    }

    public async getBranches(): Promise<import('simple-git').BranchSummary> {
        const git = this.getGit();
        try {
            const branches = await git.branch();
            return branches;
        } catch (error) {
            console.error('Failed to get branches:', error);
            vscode.window.showErrorMessage('Failed to get branches.');
            throw error;
        }
    }

    public async createBranch(branchName: string): Promise<void> {
        const git = this.getGit();
        try {
            await git.checkoutLocalBranch(branchName);
            vscode.window.showInformationMessage(`Branch '${branchName}' created and checked out.`);
        } catch (error) {
            console.error(`Failed to create branch '${branchName}':`, error);
            vscode.window.showErrorMessage(`Failed to create branch '${branchName}'.`);
            throw error;
        }
    }

    public async switchBranch(branchName: string): Promise<void> {
        const git = this.getGit();
        try {
            await git.checkout(branchName);
            vscode.window.showInformationMessage(`Switched to branch '${branchName}'.`);
        } catch (error) {
            console.error(`Failed to switch to branch '${branchName}':`, error);
            vscode.window.showErrorMessage(`Failed to switch to branch '${branchName}'.`);
            throw error;
        }
    }

    public async mergeBranch(branchName: string): Promise<void> {
        const git = this.getGit();
        try {
            // Use --no-commit to allow user to review changes before finalizing the merge.
            const result = await git.merge([branchName, '--no-commit']);
            vscode.window.showInformationMessage(`Merged branch '${branchName}' into workspace. Review changes and commit.`);
        } catch (error) {
            console.error(`Failed to merge branch '${branchName}':`, error);
            vscode.window.showErrorMessage(`Merge failed for branch '${branchName}'. Please resolve conflicts and commit.`);
            throw error;
        }
    }

    public async push(): Promise<void> {
        const git = this.getGit();
        try {
            await git.push();
            vscode.window.showInformationMessage('Pushed successfully!');
        } catch (error: any) {
            if (error.message.includes('no upstream branch')) {
                const currentBranch = await git.revparse(['--abbrev-ref', 'HEAD']);
                const remotes = await git.getRemotes(true);
                if (remotes.length > 0) {
                    const remoteName = remotes[0].name;
                    await git.push(remoteName, currentBranch, {'--set-upstream': null});
                    vscode.window.showInformationMessage(`Pushed successfully and set '${remoteName}/${currentBranch}' as upstream branch!`);
                    return;
                }
            }
            console.error('Failed to push:', error);
            vscode.window.showErrorMessage('Failed to push changes. Please check console for details.');
            throw error;
        }
    }

    public async pull(): Promise<void> {
        const git = this.getGit();
        try {
            await git.pull();
            vscode.window.showInformationMessage('Pulled successfully!');
        } catch (error) {
            console.error('Failed to pull:', error);
            vscode.window.showErrorMessage('Failed to pull changes.');
            throw error;
        }
    }

    public async fetch(): Promise<void> {
        const git = this.getGit();
        try {
            await git.fetch();
            vscode.window.showInformationMessage('Fetched successfully!');
        } catch (error) {
            console.error('Failed to fetch:', error);
            vscode.window.showErrorMessage('Failed to fetch changes.');
            throw error;
        }
    }

    public async discardChanges(filePath: string): Promise<void> {
        const git = this.getGit();
        try {
            await git.checkout(['--', filePath]);
            vscode.window.showInformationMessage(`Changes to '${filePath}' have been discarded.`);
        } catch (error) {
            console.error(`Failed to discard changes for '${filePath}':`, error);
            vscode.window.showErrorMessage(`Failed to discard changes for '${filePath}'.`);
            throw error;
        }
    }

    public async discardAllChanges(): Promise<void> {
        const git = this.getGit();
        try {
            await git.checkout(['--','.']);
            vscode.window.showInformationMessage(`All local changes have been discarded.`);
        } catch (error) {
            console.error('Failed to discard all changes:', error);
            vscode.window.showErrorMessage('Failed to discard all changes.');
            throw error;
        }
    }

    public async getStashList(): Promise<import('simple-git').LogResult> {
        const git = this.getGit();
        try {
            // The stashList method actually returns a LogResult, similar to git.log().
            const stashes = await git.stashList();
            
            // The items in `stashes.all` already contain the necessary details like `message`, `hash`, etc.
            // We just need to ensure the `ref` is present for our frontend logic, which is the `stash@{n}` string.
            // The `simple-git` `stashList` doesn't provide this directly in a field, but the message
            // often starts with it (e.g., "stash@{0}: On master: WIP").
            // We can parse it from the message or, more reliably, just add it based on the index.
            
            // The LogResult from stashList is already in a good format, but let's
            // augment the ref as the frontend depends on it.
            const allStashes = (stashes.all as any[]).map((stash, index) => {
                 return {
                    ...stash,
                    ref: `stash@{${index}}`
                };
            });

            return {
                ...stashes,
                all: allStashes,
            };

        } catch (e) {
            // In case of any error (e.g., no stashes exist), return an empty LogResult.
            console.error('Failed to get stash list:', e);
            return { all: [], total: 0, latest: null };
        }
    }

    public async createStash(message?: string): Promise<void> {
        const git = this.getGit();
        try {
            await git.stash(message ? ['save', message] : ['save']);
            vscode.window.showInformationMessage('Changes stashed successfully.');
        } catch (error) {
            console.error('Failed to create stash:', error);
            vscode.window.showErrorMessage('Failed to create stash.');
            throw error;
        }
    }

    public async popStash(stashRef: string): Promise<void> {
        const git = this.getGit();
        try {
            await git.stash(['pop', stashRef]);
            vscode.window.showInformationMessage(`Stash ${stashRef} popped successfully.`);
        } catch (e: any) {
            console.error('Failed to pop stash:', e);
            let userMessage = `Failed to pop stash: ${e.message}`;
            if (e.message && (e.message.includes('overwritten by merge') || e.message.includes('overwritten by checkout'))) {
                userMessage = 'Could not pop stash. Your current changes conflict with the stashed changes. Please commit or stash your current work before trying again.';
            }
            vscode.window.showErrorMessage(userMessage);
            throw e;
        }
    }

    public async dropStash(stashRef: string): Promise<void> {
        const git = this.getGit();
        try {
            await git.stash(['drop', stashRef]);
            vscode.window.showInformationMessage(`Stash ${stashRef} dropped successfully.`);
        } catch (e: any) {
            console.error('Failed to drop stash:', e);
            vscode.window.showErrorMessage(`Failed to drop stash: ${e.message}`);
            throw e;
        }
    }

    public async getCommitDetails(hash: string): Promise<string> {
        const git = this.getGit();
        try {
            const details = await git.show([hash]);
            return details;
        } catch (error) {
            console.error(`Failed to get details for commit '${hash}':`, error);
            vscode.window.showErrorMessage(`Failed to get details for commit '${hash}'.`);
            throw error;
        }
    }

    public async resetToCommit(hash: string): Promise<void> {
        const git = this.getGit();
        const confirmation = await vscode.window.showWarningMessage(
            `Are you sure you want to reset to commit ${hash}? This will move all subsequent changes to your workspace.`,
            { modal: true },
            'Yes'
        );

        if (confirmation === 'Yes') {
            try {
                await git.reset(['--mixed', hash]);
                vscode.window.showInformationMessage(`Successfully reset to commit ${hash}. Changes are now in your workspace.`);
            } catch (error) {
                console.error(`Failed to reset to commit '${hash}':`, error);
                vscode.window.showErrorMessage(`Failed to reset to commit '${hash}'.`);
                throw error;
            }
        }
    }

    public async createTagForCommit(hash: string): Promise<void> {
        const git = this.getGit();
        const tagName = await vscode.window.showInputBox({
            prompt: 'Enter a name for the new tag',
            placeHolder: 'e.g. v1.0.0'
        });

        if (tagName) {
            try {
                await git.tag([tagName, hash]);
                vscode.window.showInformationMessage(`Tag '${tagName}' created successfully for commit ${hash}.`);
            } catch (error: any) {
                console.error(`Failed to create tag '${tagName}':`, error);
                let userMessage = `Failed to create tag '${tagName}'.`;
                if (error.message && error.message.includes('already exists')) {
                    userMessage = `Failed to create tag. A tag named '${tagName}' already exists.`;
                }
                vscode.window.showErrorMessage(userMessage);
                throw error;
            }
        }
    }

    public async deleteLocalBranch(branchName: string): Promise<void> {
        const git = this.getGit();
        try {
            await git.deleteLocalBranch(branchName, true); // true for force delete
            vscode.window.showInformationMessage(`Branch '${branchName}' deleted successfully.`);
        } catch (error) {
            console.error(`Failed to delete local branch '${branchName}':`, error);
            vscode.window.showErrorMessage(`Failed to delete local branch '${branchName}'.`);
            throw error;
        }
    }

    public async deleteRemoteBranch(branchName: string): Promise<void> {
        const git = this.getGit();
        // Remote branch names are often in the form 'remotes/origin/branch-name'
        // We need to parse this to get the remote and the actual branch name.
        const parts = branchName.replace('remotes/', '').split('/');
        if (parts.length < 2) {
            vscode.window.showErrorMessage(`Invalid remote branch name format: ${branchName}`);
            return;
        }
        const remote = parts[0];
        const localBranchName = parts.slice(1).join('/');

        try {
            await git.push(remote, localBranchName, {'--delete': null});
            vscode.window.showInformationMessage(`Remote branch '${localBranchName}' deleted successfully.`);
        } catch (error) {
            console.error(`Failed to delete remote branch '${branchName}':`, error);
            vscode.window.showErrorMessage(`Failed to delete remote branch '${branchName}'.`);
            throw error;
        }
    }
} 